﻿using System;

namespace ITSP_Instance_Generator
{
    class SA_Instance : ICloneable
    {
        public int NumberOfNodes; // the total number of nodes (if a node is visited twice then it counts as 2 nodes)
        public Instance Instance;
        public int[] NodeOrder;
        public int[] NodeOrderLabel;
        public double TourCost;

        public SA_Instance(Instance instance, int numberOfNodes, int[] nodeOrder, int[] nodeOrderLabel, double tourCost)
        {
            this.Instance = instance;
            this.NumberOfNodes = numberOfNodes;
            this.NodeOrder = nodeOrder;
            this.NodeOrderLabel = nodeOrderLabel;
            this.TourCost = tourCost;
        }
        public SA_Instance(Instance instance)
        {
            this.Instance = instance;
            this.NumberOfNodes = Instance.DistanceMatrix.GetLength(0);
            this.NodeOrder = GenerateRandomPermutation();
            this.NodeOrderLabel = new int[NumberOfNodes];
            int NodeOrderLabelIndex = 0;
            foreach (Node node in Instance.Nodes)
                for(int i = 0; i < node.NumberOfVisits; i++)
                    NodeOrderLabel[NodeOrderLabelIndex++] = node.Number;
            UpdateTourCost();
        }

        public static Random Random = new Random();
        public int[] GenerateRandomPermutation() // knuth shuffle
        {
            int[] permutation = new int[NumberOfNodes];
            for (int i = 0; i < NumberOfNodes; i++) permutation[i] = i;
            for (int i = 0; i < NumberOfNodes; i++)
            {
                int j = Random.Next(0, NumberOfNodes);
                int swap = permutation[i];
                permutation[i] = permutation[j];
                permutation[j] = swap;
            }
            return permutation;
        }

        private void UpdateTourCost()
        {
            TourCost = CalculateTourCost();
        }
        private double CalculateTourCost()
        {
            double[] visitedAt = new double[NumberOfNodes];
            visitedAt[0] = 0;
            double totalTravelTime = 0;
            for (int i = 1; i < NumberOfNodes; i++)
            {
                visitedAt[i] = visitedAt[i-1] + Instance.DistanceMatrix[NodeOrder[i - 1]][NodeOrder[i]];
                for (int j = i-1; j >= 0; j--)
                    if (NodeOrderLabel[NodeOrder[j]] == NodeOrderLabel[NodeOrder[i]])
                        if ((visitedAt[i] - visitedAt[j]) < Instance.Bound)
                            visitedAt[i] = visitedAt[j] + Instance.Bound;
            }
            return visitedAt[NumberOfNodes-1];
        }
        private void TwoOptSwap(int i, int j)
        {
            for (int x = i; x <= (j-i+1)/2; x++)
            {
                int originalNode = NodeOrder[x];
                NodeOrder[x] = NodeOrder[j - (x - i)];
                NodeOrder[j - (x - i)] = originalNode;
            }
            UpdateTourCost();
        }

        public SA_Instance GenerateNextMove()
        {
            var nextInstance = ((SA_Instance) Clone());
            nextInstance.TwoOptSwap(Random.Next(0, NumberOfNodes), Random.Next(0, NumberOfNodes));
            return nextInstance;
        }

        public object Clone()
        {
            return new SA_Instance(
                Instance,
                NumberOfNodes,
                (int[])NodeOrder.Clone(),
                NodeOrderLabel,
                TourCost);
        }

        public String arrayToString(int[] abc)
        {
            String s = "";
            foreach (int i in abc)
            {
                s += i + " ";
            }
            return s;
        }
    }
}
